{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "03b796f5-756b-4af2-a0bd-0dfec534baf5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3.10.5 (tags/v3.10.5:f377153, Jun  6 2022, 16:14:13) [MSC v.1929 64 bit (AMD64)]\n"
     ]
    }
   ],
   "source": [
    "import sys\n",
    "print(sys.version)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ca08435b-de92-4835-ab82-ab5cb248a6fc",
   "metadata": {},
   "source": [
    "fastapi的核心功能是提供HTTP请求接口。在了解HTTP请求方式之前，我们先来了解一下“幂等”和“非幂等”。\n",
    "\n",
    "幂等性，英文是idempotent，读作[a'dmptnt]。它的含义如下：\n",
    "\n",
    "“Methods can also have the property of \"idempotence\" in that the side-effects of N > 0 identical requests is the same as for a single request.”\n",
    "\n",
    "（这句话翻译过来是这样的：方法可以有幂等性，幂等性指的是N>0次的完全相同的请求的副作用和一次单个请求的副作用是相同的）。\n",
    "\n",
    "即，如果一个方法重复执行多次，产生的效果是一样的，那么这个方法就是幂等的。\n",
    "\n",
    "最常见的HTTP请求方法是四个：GET、DELETE、POST、PUT。幂等性列举如下：\n",
    "\n",
    "| 请求方法 | 含义 | 幂等性 |  \r\n",
    "| :---: | :---: | :---: |  \r\n",
    "| GET | 用于请求指定的资源 | 是 |  \r\n",
    "| DELETE | 用于删除指定的资源 | 是 |  \r\n",
    "| POST | 用于向指定资源提交数据，数据被包含在请求体中 | 否 |  \r\n",
    "| PUT | 用于向指定资源位置上传新的内容，如果该资源已经存在则会被覆盖 \n",
    "\n",
    "\n",
    "\n",
    "GET 请求可被缓存；GET 请求保留在浏览器历史记录中；GET 请求可被收藏为书签；GET 请求不应在处理敏感数据时使用；GET 请求有长度限制；GET 请求只应当用于取回数据。\n",
    "\n",
    "\r\n",
    "POST 请求不会被缓存；POST 请求不会保留在浏览器历史记录中；POST 不能被收藏为书签；POST 请求对数据长度没有要求\n",
    "\n",
    "POST和PUT的区别\n",
    "\r\n",
    "（1） POST是用来提交数据的。提交的数据放在HTTP请求的正文里，目的在于提交数据并用于服务器端的存储，而不允许用户过多的更改相应数据（主要是相对于在url 修改要麻烦很多)\n",
    "。\r\n",
    "（2） PUT操作是幂等的。所谓幂等是指不管进行多少次操作，结果都一样。比如我用PUT修改一篇文章，然后在做同样的操作，每次操作后的资源整体结果并没有不同（文章具体内容可能有变化，但文章整体作为资源标识并没有变化\n",
    "）。\r\n",
    "（3）POST操作既不是安全的，也不是幂等的，比如常见的POST重复加载问题：当我们多次发出同样的POST请求后，其结果是创建出了若干的\n",
    "资源。\r\n",
    "（4）安全和幂等的意义在于：当操作没有达到预期的目标时，我们可以不停的重试，而不会对资源产生副作用。从这个意义上说，POST操作往往是有害的，但很多时候我们还是不得不\n",
    "使用它。\r\n",
    "（5）创建操作可以使用POST，也可以使用PUT，区别在于POST 是作用在一个集合资源之上的（/articles），而PUT操作是作用在一个具体资源之上的（/articles/id123）；再通俗点说，如果URL可以在客户端确定，那么就使用PUT，如果是在服务端确定，那么就使用POST，比如说很多资源使用数据库自增主键作为标识信息，而创建的资源的标识信息到底是什么只能由服务端提供，这个时候就必须使用POST。。| 是 |"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "deaa328d-244c-4dcd-a57b-4d4a9d6ad2ee",
   "metadata": {},
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "6d377b60-6f59-43b6-bce9-4401ed2ee3a7",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Started server process [16524]\n",
      "INFO:     Waiting for application startup.\n",
      "INFO:     Application startup complete.\n",
      "INFO:     Uvicorn running on http://0.0.0.0:8009 (Press CTRL+C to quit)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:     127.0.0.1:56730 - \"GET / HTTP/1.1\" 200 OK\n",
      "INFO:     127.0.0.1:56730 - \"GET /favicon.ico HTTP/1.1\" 404 Not Found\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Shutting down\n",
      "INFO:     Waiting for application shutdown.\n",
      "INFO:     Application shutdown complete.\n",
      "INFO:     Finished server process [16524]\n"
     ]
    }
   ],
   "source": [
    "import uvicorn\n",
    "from fastapi import FastAPI\n",
    "app = FastAPI()\n",
    "@app.get(\"/\")\n",
    "async def root():\n",
    "    return {\"message\": \"Hello World\"}\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    config = uvicorn.Config(app, host='0.0.0.0', port=8009)\n",
    "    server = uvicorn.Server(config)\n",
    "    await server.serve()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3ee70188-f531-4dac-88e9-e192c264b901",
   "metadata": {},
   "source": [
    "查看  \r\n",
    "打开浏览器访问 http://127.0.0.1:8000  。\r\n",
    "\r\n",
    "你将看到如下的 JSON \n",
    "```python\n",
    "{\"message\": \"Hello World\"}\n",
    "```\n",
    "交互式 API 文档  \r\n",
    "跳转到 http://127.0.0.1:8000/docs。\r\n",
    "\r\n",
    "你将会看到自动生成的交互式 API 文档（由 Swagger UI 提供）：响应："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4cedde72-73b4-4597-bf5a-616dd8bb2508",
   "metadata": {},
   "source": [
    "在开发 API 时，你通常使用特定的 HTTP 方法去执行特定的行为。\r\n",
    "\r\n",
    "通常使用：\r\n",
    "\r\n",
    "POST：创\n",
    "建数据。\r\n",
    "GET：\n",
    "读取数据。\r\n",
    "PUT\n",
    "：更新数据。\r\n",
    "DELET\n",
    "E：删除数据。\r\n",
    "因此，在 OpenAPI 中，每一个 HTTP 方法都被称为「操作」。\r\n",
    "\r\n",
    "我们也打算称呼它们为「操作」。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0746f61c-2171-49b9-8773-e67a77853a36",
   "metadata": {},
   "source": [
    "## 路径参数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "3141ed22-72b6-46bc-88a1-488a7d6d3a8f",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Started server process [16524]\n",
      "INFO:     Waiting for application startup.\n",
      "INFO:     Application startup complete.\n",
      "INFO:     Uvicorn running on http://0.0.0.0:8009 (Press CTRL+C to quit)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:     127.0.0.1:53974 - \"GET / HTTP/1.1\" 404 Not Found\n",
      "INFO:     127.0.0.1:53975 - \"GET /items/3 HTTP/1.1\" 200 OK\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Shutting down\n",
      "INFO:     Waiting for application shutdown.\n",
      "INFO:     Application shutdown complete.\n",
      "INFO:     Finished server process [16524]\n"
     ]
    }
   ],
   "source": [
    "import uvicorn\n",
    "from fastapi import FastAPI\n",
    "app = FastAPI()\n",
    "@app.get(\"/items/{item_id}\")\n",
    "async def read_item(item_id: int):\n",
    "    return {\"item_id\": item_id}\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    config = uvicorn.Config(app, host='0.0.0.0', port=8009)\n",
    "    server = uvicorn.Server(config)\n",
    "    await server.serve()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d4b1bcc0-4baa-4238-a2dc-462b654f9be0",
   "metadata": {},
   "source": [
    "浏览器中打开http://127.0.0.1:8009/items/3\n",
    "\n",
    "会看到{\"item_id\":3}\n",
    "\n",
    "路径参数 item_id 的值将作为参数 item_id 传递给你的函数。\n",
    "\n",
    "item_id 被声明为 int 类型。\n",
    "\n",
    "这将为你的函数提供编辑器支持，包括错误检查、代码补全等等。\n",
    "\n",
    "但如果你通过浏览器访问 http://127.0.0.1:8009/items/foo，你会看到一个清晰可读的 HTTP 错误\n",
    "\n",
    "如果你提供的是 float 而非整数也会出现同样的错误，比如： http://127.0.0.1:8009/items/4.2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bdef30c7-43ad-4c1b-bc39-1e164eb56401",
   "metadata": {},
   "source": [
    "## Pydantic"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "440d0cff-1e88-4ee6-a1f1-b9a131e48d62",
   "metadata": {},
   "source": [
    "所有的数据校验都由 Pydantic 在幕后完成，所以你可以从它所有的优点中受益。并且你知道它在这方面非常胜任。\n",
    "\n",
    "你可以使用同样的类型声明来声明 str、float、bool 以及许多其他的复合数据类型。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b6481ee-34c6-46c1-abdd-6cd41c11dbea",
   "metadata": {},
   "source": [
    "## 预设值"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a77d3f03-f067-4589-abaf-9c34ddef4db7",
   "metadata": {},
   "source": [
    "如果你有一个接收路径参数的路径操作，但你希望预先设定可能的有效参数值，则可以使用标准的 Python Enum 类型。\n",
    "\n",
    "创建一个 Enum 类\r\n",
    "导入 Enum 并创建一个继承自 str 和 Enum 的子类。\r\n",
    "\r\n",
    "通过从 str 继承，API 文档将能够知道这些值必须为 string 类型并且能够正确地展示出来。\r\n",
    "\r\n",
    "然后创建具有固定值class ModelName(str, Enum)，的类属性，这些固定值将是可\n",
    "\n",
    "然后使用你定义的枚举类（ModelName）创建一个带有类型标注的路径参数async def get_model(model_name: ModelName)的有效值："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "1cecc860-833c-486b-b34a-d9fed1cbf2fc",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Started server process [16524]\n",
      "INFO:     Waiting for application startup.\n",
      "INFO:     Application startup complete.\n",
      "INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:     127.0.0.1:54160 - \"GET /models/resnet HTTP/1.1\" 200 OK\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Shutting down\n",
      "INFO:     Waiting for application shutdown.\n",
      "INFO:     Application shutdown complete.\n",
      "INFO:     Finished server process [16524]\n"
     ]
    }
   ],
   "source": [
    "import uvicorn\n",
    "from fastapi import FastAPI\n",
    "from enum import Enum\n",
    "\n",
    "class ModelName(str, Enum):\n",
    "    alexnet = \"alexnet\"\n",
    "    resnet = \"resnet\"\n",
    "    lenet = \"lenet\"\n",
    "\n",
    "app = FastAPI()\n",
    "\n",
    "@app.get(\"/models/{model_name}\")\n",
    "async def get_model(model_name: ModelName):\n",
    "    if model_name is ModelName.alexnet:\n",
    "        return {\"model_name\": model_name, \"message\": \"Deep Learning FTW!\"}\n",
    "\n",
    "    if model_name.value == \"lenet\":\n",
    "        return {\"model_name\": model_name, \"message\": \"LeCNN all the images\"}\n",
    "\n",
    "    return {\"model_name\": model_name, \"message\": \"Have some residuals\"}\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    config = uvicorn.Config(app, host='0.0.0.0', port=8000)\n",
    "    server = uvicorn.Server(config)\n",
    "    await server.serve()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5f68c73c-a52b-4a07-add3-bd58e1eb81be",
   "metadata": {},
   "source": [
    "在浏览器中输入http://127.0.0.1:8000/models/resnet\n",
    "\n",
    "会看到：\n",
    "{\"model_name\":\"resnet\",\"message\":\"Have some residuals\"}\n",
    "\n",
    "models/ 后面只能是'alexnet', 'resnet' or 'lenet'，如果输入其他字符，会显示\n",
    "{\"detail\":[{\"type\":\"enum\",\"loc\":[\"path\",\"model_name\"],\"msg\":\"Input should be 'alexnet', 'resnet' or 'lenet'\",\"input\":\"resne\",\"ctx\":{\"expected\":\"'alexnet', 'resnet' or 'lenet'\"}}]}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "46b70118-572a-4f9c-a88c-76ca8b6a6885",
   "metadata": {},
   "source": [
    "## 获取枚举值"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fea56dc4-8ef7-4122-800d-7ee09ee6ce8d",
   "metadata": {},
   "source": [
    "你可以使用 model_name.value 或通常来说 your_enum_member.value 来获取实际的值（在这个例子中为 str）"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "32a6a88f-5881-43ec-931a-31638fd7ca23",
   "metadata": {},
   "source": [
    "## 包含路径的路径参数"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5c59212d-80f9-4368-a6a9-f23e760b39fe",
   "metadata": {},
   "source": [
    "假设你有一个路径操作，它的路径为 /files/{file_path}。\n",
    "\n",
    "但是你需要 file_path 自身也包含路径，比如 home/johndoe/myfile.txt。\n",
    "\n",
    "因此，该文件的URL将类似于这样：/files/home/johndoe/myfile.txt。\n",
    "\n",
    "你可以使用直接来自 Starlette 的选项来声明一个包含路径的路径参数：\n",
    "\n",
    "/files/{file_path:path}\n",
    "\n",
    "在这种情况下，参数的名称为 file_path，结尾部分的 :path 说明该参数应匹配任意的路径。\n",
    "\n",
    "因此，你可以这样使用它："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b4b94c49-1f74-4c8a-9b58-7334672adb2e",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Started server process [16524]\n",
      "INFO:     Waiting for application startup.\n",
      "INFO:     Application startup complete.\n",
      "INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:     127.0.0.1:54386 - \"GET /files/home/johndoe/myfile.txt HTTP/1.1\" 200 OK\n",
      "INFO:     127.0.0.1:54398 - \"GET /files//home/johndoe/myfile.txt HTTP/1.1\" 200 OK\n",
      "INFO:     127.0.0.1:54398 - \"GET /files//home/johndoe/myfile.txt HTTP/1.1\" 200 OK\n"
     ]
    }
   ],
   "source": [
    "import uvicorn\n",
    "from fastapi import FastAPI\n",
    "app = FastAPI()\n",
    "@app.get(\"/files/{file_path:path}\")\n",
    "async def read_file(file_path: str):\n",
    "    return {\"file_path\": file_path}\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    config = uvicorn.Config(app, host='0.0.0.0', port=8000)\n",
    "    server = uvicorn.Server(config)\n",
    "    await server.serve()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a7f9f69a-7752-4b5f-b0e0-2aa8cac6ea66",
   "metadata": {},
   "source": [
    "打开浏览器，输入 http://127.0.0.1:8000/files/home/johndoe/myfile.txt\n",
    "\n",
    "会看到：\n",
    "{\"file_path\":\"home/johndoe/myfile.txt\"}"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
